/*
 * Copyright (C) 2003-2012 Free Software Foundation, Inc.
 *
 * Author: Nikos Mavrogiannopoulos
 *
 * This file is part of GnuTLS.
 *
 * The GnuTLS is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

#include <gnutls_int.h>
#include <libtasn1.h>
#include <gnutls_datum.h>
#include <gnutls_global.h>
#include <gnutls_errors.h>
#include <gnutls_str.h>
#include <gnutls_x509.h>
#include <gnutls_num.h>
#include <x509_b64.h>
#include "x509_int.h"
#include <common.h>

struct oid2string
{
  const char *oid;
  const char *ldap_desc;
  int choice;                   /* of type DirectoryString */
  int printable;
  const char *asn_desc;         /* description in the pkix file */
};

/* This list contains all the OIDs that may be
 * contained in a rdnSequence and are printable.
 */
static const struct oid2string _oid2str[] = {
  /* PKIX
   */
  {"1.3.6.1.5.5.7.9.1", "dateOfBirth", 0, 1, "PKIX1.GeneralizedTime"},
  {"1.3.6.1.5.5.7.9.2", "placeOfBirth", 0, 1, "PKIX1.DirectoryString"},
  {"1.3.6.1.5.5.7.9.3", "gender", 0, 1, "PKIX1.PrintableString"},
  {"1.3.6.1.5.5.7.9.4", "countryOfCitizenship", 0, 1,
   "PKIX1.PrintableString"},
  {"1.3.6.1.5.5.7.9.5", "countryOfResidence", 0, 1, "PKIX1.PrintableString"},

  {"2.5.4.6", "C", 0, 1, "PKIX1.PrintableString"},
  {"2.5.4.9", "STREET", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.12", "T", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.10", "O", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.11", "OU", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.3", "CN", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.7", "L", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.8", "ST", 1, 1, "PKIX1.DirectoryString"},

  {"2.5.4.5", "serialNumber", 0, 1, "PKIX1.PrintableString"},
  {"2.5.4.20", "telephoneNumber", 0, 1, "PKIX1.PrintableString"},
  {"2.5.4.4", "surName", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.43", "initials", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.44", "generationQualifier", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.42", "givenName", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.65", "pseudonym", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.46", "dnQualifier", 0, 1, "PKIX1.PrintableString"},
  {"2.5.4.17", "postalCode", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.41", "Name", 1, 1, "PKIX1.DirectoryString"},
  {"2.5.4.15", "businessCategory", 1, 1, "PKIX1.DirectoryString"},

  {"0.9.2342.19200300.100.1.25", "DC", 0, 1, "PKIX1.IA5String"},
  {"0.9.2342.19200300.100.1.1", "UID", 1, 1, "PKIX1.DirectoryString"},

  /* Extended validation
   */
  {"1.3.6.1.4.1.311.60.2.1.1", "jurisdictionOfIncorporationLocalityName", 1,
   1, "PKIX1.DirectoryString"},
  {"1.3.6.1.4.1.311.60.2.1.2",
   "jurisdictionOfIncorporationStateOrProvinceName", 1, 1,
   "PKIX1.DirectoryString"},
  {"1.3.6.1.4.1.311.60.2.1.3", "jurisdictionOfIncorporationCountryName", 0, 1,
   "PKIX1.PrintableString"},

  /* PKCS #9
   */
  {"1.2.840.113549.1.9.1", "EMAIL", 0, 1, "PKIX1.IA5String"},
  {"1.2.840.113549.1.9.7", NULL, 1, 1, "PKIX1.pkcs-9-challengePassword"},

  /* friendly name */
  {"1.2.840.113549.1.9.20", NULL, 0, 1, "PKIX1.BMPString"},
  /* local key id */
  {"1.2.840.113549.1.9.21", NULL, 0, 1, "PKIX1.pkcs-9-localKeyId"},

  /* rfc3920 section 5.1.1 */
  {"1.3.6.1.5.5.7.8.5", "XmppAddr", 0, 1, "PKIX1.UTF8String"},

  {NULL, NULL, 0, 0, ""}
};

/* Returns 1 if the data defined by the OID are printable.
 */
int
_gnutls_x509_oid_data_printable (const char *oid)
{
  unsigned int i = 0;

  do
    {
      if (strcmp (_oid2str[i].oid, oid) == 0)
        return _oid2str[i].printable;
      i++;
    }
  while (_oid2str[i].oid != NULL);

  return 0;
}

/**
 * gnutls_x509_dn_oid_known:
 * @oid: holds an Object Identifier in a null terminated string
 *
 * This function will inform about known DN OIDs. This is useful since
 * functions like gnutls_x509_crt_set_dn_by_oid() use the information
 * on known OIDs to properly encode their input. Object Identifiers
 * that are not known are not encoded by these functions, and their
 * input is stored directly into the ASN.1 structure. In that case of
 * unknown OIDs, you have the responsibility of DER encoding your
 * data.
 *
 * Returns: 1 on known OIDs and 0 otherwise.
 **/
int
gnutls_x509_dn_oid_known (const char *oid)
{
  unsigned int i = 0;

  do
    {
      if (strcmp (_oid2str[i].oid, oid) == 0)
        return 1;
      i++;
    }
  while (_oid2str[i].oid != NULL);

  return 0;
}

/* Returns 1 if the data defined by the OID are of a choice
 * type.
 */
int
_gnutls_x509_oid_data_choice (const char *oid)
{
  unsigned int i = 0;

  do
    {
      if (strcmp (_oid2str[i].oid, oid) == 0)
        return _oid2str[i].choice;
      i++;
    }
  while (_oid2str[i].oid != NULL);

  return 0;
}

/**
 * gnutls_x509_dn_oid_name:
 * @oid: holds an Object Identifier in a null terminated string
 * @flags: 0 or %GNUTLS_X509_DN_OID_*
 *
 * This function will return the name of a known DN OID. If
 * %GNUTLS_X509_DN_OID_RETURN_OID is specified this function
 * will return the given OID if no descriptive name has been
 * found.
 *
 * Returns: A null terminated string or NULL otherwise.
 *
 * Since: 3.0
 **/
const char*
gnutls_x509_dn_oid_name (const char *oid, unsigned int flags)
{
  unsigned int i = 0;

  do
    {
      if (strcmp (_oid2str[i].oid, oid) == 0)
        return _oid2str[i].ldap_desc;
      i++;
    }
  while (_oid2str[i].oid != NULL);

  if (flags & GNUTLS_X509_DN_OID_RETURN_OID) return oid;
  else return NULL;
}

const char *
_gnutls_x509_oid2asn_string (const char *oid)
{
  unsigned int i = 0;

  do
    {
      if (strcmp (_oid2str[i].oid, oid) == 0)
        return _oid2str[i].asn_desc;
      i++;
    }
  while (_oid2str[i].oid != NULL);

  return NULL;
}


/* This function will convert an attribute value, specified by the OID,
 * to a string. The result will be a null terminated string.
 *
 * res may be null. This will just return the res_size, needed to
 * hold the string.
 */
int
_gnutls_x509_oid_data2string (const char *oid, void *value,
                              int value_size, char *res, size_t * res_size)
{
  char str[MAX_STRING_LEN], tmpname[128];
  const char *ANAME = NULL;
  int CHOICE = -1, len = -1, result;
  ASN1_TYPE tmpasn = ASN1_TYPE_EMPTY;
  char asn1_err[ASN1_MAX_ERROR_DESCRIPTION_SIZE] = "";

  if (value == NULL || value_size <= 0 || res_size == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_INVALID_REQUEST;
    }

  if (_gnutls_x509_oid_data_printable (oid) == 0)
    {
      gnutls_assert ();
      return GNUTLS_E_INTERNAL_ERROR;
    }

  ANAME = _gnutls_x509_oid2asn_string (oid);
  CHOICE = _gnutls_x509_oid_data_choice (oid);

  if (ANAME == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_INTERNAL_ERROR;
    }

  if ((result =
       asn1_create_element (_gnutls_get_pkix (), ANAME,
                            &tmpasn)) != ASN1_SUCCESS)
    {
      gnutls_assert ();
      return _gnutls_asn2err (result);
    }

  if ((result =
       asn1_der_decoding (&tmpasn, value, value_size,
                          asn1_err)) != ASN1_SUCCESS)
    {
      gnutls_assert ();
      _gnutls_debug_log ("asn1_der_decoding: %s:%s\n", str, asn1_err);
      asn1_delete_structure (&tmpasn);
      return _gnutls_asn2err (result);
    }

  /* If this is a choice then we read the choice. Otherwise it
   * is the value;
   */
  len = sizeof (str) - 1;
  if ((result = asn1_read_value (tmpasn, "", str, &len)) != ASN1_SUCCESS)
    {                           /* CHOICE */
      gnutls_assert ();
      asn1_delete_structure (&tmpasn);
      return _gnutls_asn2err (result);
    }

  if (CHOICE == 0)
    {
      str[len] = 0;

      /* Refuse to deal with strings containing NULs. */
      if (strlen (str) != (size_t)len)
        return GNUTLS_E_ASN1_DER_ERROR;

      if (res)
        _gnutls_str_cpy (res, *res_size, str);
      *res_size = (size_t)len;

      asn1_delete_structure (&tmpasn);
    }
  else
    {                           /* CHOICE */
      int non_printable = 0, teletex = 0;
      str[len] = 0;

      /* Note that we do not support strings other than
       * UTF-8 (thus ASCII as well).
       */
      if (strcmp (str, "printableString") != 0 &&
          strcmp (str, "ia5String") != 0 && strcmp (str, "utf8String") != 0)
        {
          non_printable = 1;
        }
      if (strcmp (str, "teletexString") == 0)
        teletex = 1;


      _gnutls_str_cpy (tmpname, sizeof (tmpname), str);

      len = sizeof (str) - 1;
      if ((result =
           asn1_read_value (tmpasn, tmpname, str, &len)) != ASN1_SUCCESS)
        {
          asn1_delete_structure (&tmpasn);
          return _gnutls_asn2err (result);
        }

      asn1_delete_structure (&tmpasn);

      if (teletex != 0)
        {
          int ascii = 0, i;
          /* HACK: if the teletex string contains only ascii
           * characters then treat it as printable.
           */
          for (i = 0; i < len; i++)
            if (!isascii (str[i]))
              ascii = 1;

          if (ascii == 0)
            non_printable = 0;
        }

      if (non_printable == 0)
        {
          str[len] = 0;

          /* Refuse to deal with strings containing NULs. */
          if (strlen (str) != (size_t)len)
            return GNUTLS_E_ASN1_DER_ERROR;

          if (res)
            _gnutls_str_cpy (res, *res_size, str);
          *res_size = (size_t)len;
        }
      else
        {
          result = _gnutls_x509_data2hex (str, (size_t)len, res, res_size);
          if (result < 0)
            {
              gnutls_assert ();
              return result;
            }
        }
    }

  return 0;
}


/* Converts a data string to an LDAP rfc2253 hex string
 * something like '#01020304'
 */
int
_gnutls_x509_data2hex (const void * data, size_t data_size,
                       void * _out, size_t * sizeof_out)
{
  char *res;
  char escaped[MAX_STRING_LEN];
  unsigned int size;
  char* out = _out;

  if (2 * data_size + 1 > MAX_STRING_LEN)
    {
      gnutls_assert ();
      return GNUTLS_E_INTERNAL_ERROR;
    }

  res = _gnutls_bin2hex (data, data_size, escaped, sizeof (escaped), NULL);
  if (!res)
    {
      gnutls_assert ();
      return GNUTLS_E_INTERNAL_ERROR;
    }

  size = strlen (res) + 1;
  if (size + 1 > *sizeof_out)
    {
      *sizeof_out = size;
      return GNUTLS_E_SHORT_MEMORY_BUFFER;
    }
  *sizeof_out = size;           /* -1 for the null +1 for the '#' */

  if (out)
    {
      out[0] = '#';
      out[1] = 0;
      _gnutls_str_cat (out, *sizeof_out, res);
    }

  return 0;
}


/* TIME functions 
 * Convertions between generalized or UTC time to time_t
 *
 */

/* This is an emulations of the struct tm.
 * Since we do not use libc's functions, we don't need to
 * depend on the libc structure.
 */
typedef struct fake_tm
{
  int tm_mon;
  int tm_year;                  /* FULL year - ie 1971 */
  int tm_mday;
  int tm_hour;
  int tm_min;
  int tm_sec;
} fake_tm;

/* The mktime_utc function is due to Russ Allbery (rra@stanford.edu),
 * who placed it under public domain:
 */

/* The number of days in each month. 
 */
static const int MONTHDAYS[] = {
  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

    /* Whether a given year is a leap year. */
#define ISLEAP(year) \
        (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0))

/*
 **  Given a struct tm representing a calendar time in UTC, convert it to
 **  seconds since epoch.  Returns (time_t) -1 if the time is not
 **  convertable.  Note that this function does not canonicalize the provided
 **  struct tm, nor does it allow out of range values or years before 1970.
 */
static time_t
mktime_utc (const struct fake_tm *tm)
{
  time_t result = 0;
  int i;

/* We do allow some ill-formed dates, but we don't do anything special
 * with them and our callers really shouldn't pass them to us.  Do
 * explicitly disallow the ones that would cause invalid array accesses
 * or other algorithm problems. 
 */
  if (tm->tm_mon < 0 || tm->tm_mon > 11 || tm->tm_year < 1970)
    return (time_t) - 1;

/* Convert to a time_t. 
 */
  for (i = 1970; i < tm->tm_year; i++)
    result += 365 + ISLEAP (i);
  for (i = 0; i < tm->tm_mon; i++)
    result += MONTHDAYS[i];
  if (tm->tm_mon > 1 && ISLEAP (tm->tm_year))
    result++;
  result = 24 * (result + tm->tm_mday - 1) + tm->tm_hour;
  result = 60 * result + tm->tm_min;
  result = 60 * result + tm->tm_sec;
  return result;
}


/* this one will parse dates of the form:
 * month|day|hour|minute|sec* (2 chars each)
 * and year is given. Returns a time_t date.
 */
static time_t
time2gtime (const char *ttime, int year)
{
  char xx[4];
  struct fake_tm etime;

  if (strlen (ttime) < 8)
    {
      gnutls_assert ();
      return (time_t) - 1;
    }

  etime.tm_year = year;

  /* In order to work with 32 bit
   * time_t.
   */
  if (sizeof (time_t) <= 4 && etime.tm_year >= 2038)
    return (time_t) 2145914603; /* 2037-12-31 23:23:23 */

  if (etime.tm_year < 1970)
    return (time_t) 0;

  xx[2] = 0;

/* get the month
 */
  memcpy (xx, ttime, 2);        /* month */
  etime.tm_mon = atoi (xx) - 1;
  ttime += 2;

/* get the day
 */
  memcpy (xx, ttime, 2);        /* day */
  etime.tm_mday = atoi (xx);
  ttime += 2;

/* get the hour
 */
  memcpy (xx, ttime, 2);        /* hour */
  etime.tm_hour = atoi (xx);
  ttime += 2;

/* get the minutes
 */
  memcpy (xx, ttime, 2);        /* minutes */
  etime.tm_min = atoi (xx);
  ttime += 2;

  if (strlen (ttime) >= 2)
    {
      memcpy (xx, ttime, 2);
      etime.tm_sec = atoi (xx);
      ttime += 2;
    }
  else
    etime.tm_sec = 0;

  return mktime_utc (&etime);
}


/* returns a time_t value that contains the given time.
 * The given time is expressed as:
 * YEAR(2)|MONTH(2)|DAY(2)|HOUR(2)|MIN(2)|SEC(2)*
 *
 * (seconds are optional)
 */
static time_t
utcTime2gtime (const char *ttime)
{
  char xx[3];
  int year;

  if (strlen (ttime) < 10)
    {
      gnutls_assert ();
      return (time_t) - 1;
    }
  xx[2] = 0;
/* get the year
 */
  memcpy (xx, ttime, 2);        /* year */
  year = atoi (xx);
  ttime += 2;

  if (year > 49)
    year += 1900;
  else
    year += 2000;

  return time2gtime (ttime, year);
}

/* returns a time_t value that contains the given time.
 * The given time is expressed as:
 * YEAR(4)|MONTH(2)|DAY(2)|HOUR(2)|MIN(2)|SEC(2)*
 */
time_t
_gnutls_x509_generalTime2gtime (const char *ttime)
{
  char xx[5];
  int year;

  if (strlen (ttime) < 12)
    {
      gnutls_assert ();
      return (time_t) - 1;
    }

  if (strchr (ttime, 'Z') == 0)
    {
      gnutls_assert ();
      /* sorry we don't support it yet
       */
      return (time_t) - 1;
    }
  xx[4] = 0;

/* get the year
 */
  memcpy (xx, ttime, 4);        /* year */
  year = atoi (xx);
  ttime += 4;

  return time2gtime (ttime, year);
}

static int
gtime2generalTime (time_t gtime, char *str_time, size_t str_time_size)
{
  size_t ret;
  struct tm _tm;

  if (!gmtime_r (&gtime, &_tm))
    {
      gnutls_assert ();
      return GNUTLS_E_INTERNAL_ERROR;
    }

  ret = strftime (str_time, str_time_size, "%Y%m%d%H%M%SZ", &_tm);
  if (!ret)
    {
      gnutls_assert ();
      return GNUTLS_E_SHORT_MEMORY_BUFFER;
    }


  return 0;
}


/* Extracts the time in time_t from the ASN1_TYPE given. When should
 * be something like "tbsCertList.thisUpdate".
 */
#define MAX_TIME 64
time_t
_gnutls_x509_get_time (ASN1_TYPE c2, const char *when, int nochoice)
{
  char ttime[MAX_TIME];
  char name[128];
  time_t c_time = (time_t) - 1;
  int len, result;

  len = sizeof (ttime) - 1;
  result = asn1_read_value (c2, when, ttime, &len);
  if (result != ASN1_SUCCESS)
    {
      gnutls_assert ();
      return (time_t) (-1);
    }

  if (nochoice != 0)
    {
      c_time = _gnutls_x509_generalTime2gtime (ttime);
    }
  else
    {
      _gnutls_str_cpy (name, sizeof (name), when);

      /* CHOICE */
      if (strcmp (ttime, "generalTime") == 0)
        {
          _gnutls_str_cat (name, sizeof (name), ".generalTime");
          len = sizeof (ttime) - 1;
          result = asn1_read_value (c2, name, ttime, &len);
          if (result == ASN1_SUCCESS)
            c_time = _gnutls_x509_generalTime2gtime (ttime);
        }
      else
        {                           /* UTCTIME */
          _gnutls_str_cat (name, sizeof (name), ".utcTime");
          len = sizeof (ttime) - 1;
          result = asn1_read_value (c2, name, ttime, &len);
          if (result == ASN1_SUCCESS)
            c_time = utcTime2gtime (ttime);
        }

      /* We cannot handle dates after 2031 in 32 bit machines.
       * a time_t of 64bits has to be used.
       */
      if (result != ASN1_SUCCESS)
        {
          gnutls_assert ();
          return (time_t) (-1);
        }
    }

  return c_time;
}

/* Sets the time in time_t in the ASN1_TYPE given. Where should
 * be something like "tbsCertList.thisUpdate".
 */
int
_gnutls_x509_set_time (ASN1_TYPE c2, const char *where, time_t tim, int nochoice)
{
  char str_time[MAX_TIME];
  char name[128];
  int result, len;

  if (nochoice != 0)
    {
      result = gtime2generalTime( tim, str_time, sizeof(str_time));
      if (result < 0)
        return gnutls_assert_val(result);
      
      len = strlen (str_time);
      result = asn1_write_value(c2, where, str_time, len);
      if (result != ASN1_SUCCESS)
        return gnutls_assert_val(_gnutls_asn2err (result));
        
      return 0;
    }

  _gnutls_str_cpy (name, sizeof (name), where);

  if ((result = asn1_write_value (c2, name, "generalTime", 1)) < 0)
    {
      gnutls_assert ();
      return _gnutls_asn2err (result);
    }

  result = gtime2generalTime (tim, str_time, sizeof (str_time));
  if (result < 0)
    {
      gnutls_assert ();
      return result;
    }

  _gnutls_str_cat (name, sizeof (name), ".generalTime");

  len = strlen (str_time);
  result = asn1_write_value (c2, name, str_time, len);
  if (result != ASN1_SUCCESS)
    {
      gnutls_assert ();
      return _gnutls_asn2err (result);
    }

  return 0;
}


gnutls_x509_subject_alt_name_t
_gnutls_x509_san_find_type (char *str_type)
{
  if (strcmp (str_type, "dNSName") == 0)
    return GNUTLS_SAN_DNSNAME;
  if (strcmp (str_type, "rfc822Name") == 0)
    return GNUTLS_SAN_RFC822NAME;
  if (strcmp (str_type, "uniformResourceIdentifier") == 0)
    return GNUTLS_SAN_URI;
  if (strcmp (str_type, "iPAddress") == 0)
    return GNUTLS_SAN_IPADDRESS;
  if (strcmp (str_type, "otherName") == 0)
    return GNUTLS_SAN_OTHERNAME;
  if (strcmp (str_type, "directoryName") == 0)
    return GNUTLS_SAN_DN;
  return (gnutls_x509_subject_alt_name_t) - 1;
}

/* A generic export function. Will export the given ASN.1 encoded data
 * to PEM or DER raw data.
 */
int
_gnutls_x509_export_int_named (ASN1_TYPE asn1_data, const char *name,
                               gnutls_x509_crt_fmt_t format,
                               const char *pem_header,
                               unsigned char *output_data,
                               size_t * output_data_size)
{
  int result, len;

  if (format == GNUTLS_X509_FMT_DER)
    {

      if (output_data == NULL)
        *output_data_size = 0;

      len = *output_data_size;

      if ((result =
           asn1_der_coding (asn1_data, name, output_data, &len,
                            NULL)) != ASN1_SUCCESS)
        {
          *output_data_size = (size_t)len;
          if (result == ASN1_MEM_ERROR)
            {
              return GNUTLS_E_SHORT_MEMORY_BUFFER;
            }
          gnutls_assert ();
          return _gnutls_asn2err (result);
        }

      *output_data_size = (size_t)len;

    }
  else
    {                           /* PEM */
      uint8_t *out;
      gnutls_datum_t tmp;

      result = _gnutls_x509_der_encode (asn1_data, name, &tmp, 0);
      if (result < 0)
        {
          gnutls_assert ();
          return result;
        }

      result = _gnutls_fbase64_encode (pem_header, tmp.data, tmp.size, &out);

      _gnutls_free_datum (&tmp);

      if (result < 0)
        {
          gnutls_assert ();
          return result;
        }

      if (result == 0)
        {                       /* oooops */
          gnutls_assert ();
          return GNUTLS_E_INTERNAL_ERROR;
        }

      if ((size_t) result > *output_data_size)
        {
          gnutls_assert ();
          gnutls_free (out);
          *output_data_size = (size_t)result;
          return GNUTLS_E_SHORT_MEMORY_BUFFER;
        }

      *output_data_size = (size_t)result;

      if (output_data)
        {
          memcpy (output_data, out, (size_t)result);

          /* do not include the null character into output size.
           */
          *output_data_size = (size_t)result - 1;
        }
      gnutls_free (out);

    }

  return 0;
}

int
_gnutls_x509_export_int (ASN1_TYPE asn1_data,
                         gnutls_x509_crt_fmt_t format,
                         const char *pem_header,
                         unsigned char *output_data,
                         size_t * output_data_size)
{
  return _gnutls_x509_export_int_named (asn1_data, "",
                                        format, pem_header, output_data,
                                        output_data_size);
}

/* Decodes an octet string. Leave string_type null for a normal
 * octet string. Otherwise put something like BMPString, PrintableString
 * etc.
 */
int
_gnutls_x509_decode_octet_string (const char *string_type,
                                  const uint8_t * der, size_t der_size,
                                  uint8_t * output, size_t * output_size)
{
  ASN1_TYPE c2 = ASN1_TYPE_EMPTY;
  int result, tmp_output_size;
  char strname[64];

  if (string_type == NULL)
    _gnutls_str_cpy (strname, sizeof (strname), "PKIX1.pkcs-7-Data");
  else
    {
      _gnutls_str_cpy (strname, sizeof (strname), "PKIX1.");
      _gnutls_str_cat (strname, sizeof (strname), string_type);
    }

  if ((result = asn1_create_element
       (_gnutls_get_pkix (), strname, &c2)) != ASN1_SUCCESS)
    {
      gnutls_assert ();
      result = _gnutls_asn2err (result);
      goto cleanup;
    }

  result = asn1_der_decoding (&c2, der, der_size, NULL);
  if (result != ASN1_SUCCESS)
    {
      gnutls_assert ();
      result = _gnutls_asn2err (result);
      goto cleanup;
    }

  tmp_output_size = *output_size;
  result = asn1_read_value (c2, "", output, &tmp_output_size);
  *output_size = (size_t)tmp_output_size;

  if (result != ASN1_SUCCESS)
    {
      gnutls_assert ();
      result = _gnutls_asn2err (result);
      goto cleanup;
    }

  result = 0;

cleanup:
  if (c2)
    asn1_delete_structure (&c2);

  return result;
}


/* Reads a value from an ASN1 tree, and puts the output
 * in an allocated variable in the given datum.
 * flags == 0 do nothing  with the DER output
 * flags == 1 parse the DER output as OCTET STRING
 * flags == 2 the value is a BIT STRING
 */
int
_gnutls_x509_read_value (ASN1_TYPE c, const char *root,
                         gnutls_datum_t * ret, int flags)
{
  int len = 0, result;
  size_t slen;
  uint8_t *tmp = NULL;

  result = asn1_read_value (c, root, NULL, &len);
  if (result != ASN1_MEM_ERROR)
    {
      gnutls_assert ();
      result = _gnutls_asn2err (result);
      return result;
    }

  if (flags == 2)
    len /= 8;

  tmp = gnutls_malloc ((size_t)len);
  if (tmp == NULL)
    {
      gnutls_assert ();
      result = GNUTLS_E_MEMORY_ERROR;
      goto cleanup;
    }

  result = asn1_read_value (c, root, tmp, &len);
  if (result != ASN1_SUCCESS)
    {
      gnutls_assert ();
      result = _gnutls_asn2err (result);
      goto cleanup;
    }

  if (flags == 2)
    len /= 8;

  /* Extract the OCTET STRING.
   */

  if (flags == 1)
    {
      slen = (size_t)len;
      result = _gnutls_x509_decode_octet_string (NULL, tmp, slen, tmp, &slen);
      if (result < 0)
        {
          gnutls_assert ();
          goto cleanup;
        }
      len = slen;
    }

  ret->data = tmp;
  ret->size = (unsigned)len;

  return 0;

cleanup:
  gnutls_free (tmp);
  return result;

}

/* DER Encodes the src ASN1_TYPE and stores it to
 * the given datum. If str is non zero then the data are encoded as
 * an OCTET STRING.
 */
int
_gnutls_x509_der_encode (ASN1_TYPE src, const char *src_name,
                         gnutls_datum_t * res, int str)
{
  int size, result;
  int asize;
  uint8_t *data = NULL;
  ASN1_TYPE c2 = ASN1_TYPE_EMPTY;

  size = 0;
  result = asn1_der_coding (src, src_name, NULL, &size, NULL);
  if (result != ASN1_MEM_ERROR)
    {
      gnutls_assert ();
      result = _gnutls_asn2err (result);
      goto cleanup;
    }

  /* allocate data for the der
   */

  if (str)
    size += 16;                 /* for later to include the octet tags */
  asize = size;

  data = gnutls_malloc ((size_t)size);
  if (data == NULL)
    {
      gnutls_assert ();
      result = GNUTLS_E_MEMORY_ERROR;
      goto cleanup;
    }

  result = asn1_der_coding (src, src_name, data, &size, NULL);
  if (result != ASN1_SUCCESS)
    {
      gnutls_assert ();
      result = _gnutls_asn2err (result);
      goto cleanup;
    }

  if (str)
    {
      if ((result = asn1_create_element
           (_gnutls_get_pkix (), "PKIX1.pkcs-7-Data", &c2)) != ASN1_SUCCESS)
        {
          gnutls_assert ();
          result = _gnutls_asn2err (result);
          goto cleanup;
        }

      result = asn1_write_value (c2, "", data, size);
      if (result != ASN1_SUCCESS)
        {
          gnutls_assert ();
          result = _gnutls_asn2err (result);
          goto cleanup;
        }

      result = asn1_der_coding (c2, "", data, &asize, NULL);
      if (result != ASN1_SUCCESS)
        {
          gnutls_assert ();
          result = _gnutls_asn2err (result);
          goto cleanup;
        }

      size = asize;

      asn1_delete_structure (&c2);
    }

  res->data = data;
  res->size = (unsigned)size;
  return 0;

cleanup:
  gnutls_free (data);
  asn1_delete_structure (&c2);
  return result;

}

/* DER Encodes the src ASN1_TYPE and stores it to
 * dest in dest_name. Useful to encode something and store it
 * as OCTET. If str is non null then the data are encoded as
 * an OCTET STRING.
 */
int
_gnutls_x509_der_encode_and_copy (ASN1_TYPE src, const char *src_name,
                                  ASN1_TYPE dest, const char *dest_name,
                                  int str)
{
  int result;
  gnutls_datum_t encoded;

  result = _gnutls_x509_der_encode (src, src_name, &encoded, str);

  if (result < 0)
    {
      gnutls_assert ();
      return result;
    }

  /* Write the data.
   */
  result = asn1_write_value (dest, dest_name, encoded.data, (int)encoded.size);

  _gnutls_free_datum (&encoded);

  if (result != ASN1_SUCCESS)
    {
      gnutls_assert ();
      return _gnutls_asn2err (result);
    }

  return 0;
}

/* Writes the value of the datum in the given ASN1_TYPE. If str is non
 * (0) it encodes it as OCTET STRING.
 */
int
_gnutls_x509_write_value (ASN1_TYPE c, const char *root,
                          const gnutls_datum_t * data, int str)
{
  int result;
  ASN1_TYPE c2 = ASN1_TYPE_EMPTY;
  gnutls_datum_t val = { NULL, 0 };

  if (str)
    {
      /* Convert it to OCTET STRING
       */
      if ((result = asn1_create_element
           (_gnutls_get_pkix (), "PKIX1.pkcs-7-Data", &c2)) != ASN1_SUCCESS)
        {
          gnutls_assert ();
          result = _gnutls_asn2err (result);
          goto cleanup;
        }

      result = asn1_write_value (c2, "", data->data, data->size);
      if (result != ASN1_SUCCESS)
        {
          gnutls_assert ();
          result = _gnutls_asn2err (result);
          goto cleanup;
        }

      result = _gnutls_x509_der_encode (c2, "", &val, 0);
      if (result < 0)
        {
          gnutls_assert ();
          goto cleanup;
        }

    }
  else
    {
      val.data = data->data;
      val.size = data->size;
    }

  /* Write the data.
   */
  result = asn1_write_value (c, root, val.data, val.size);
  if (result != ASN1_SUCCESS)
    {
      gnutls_assert ();
      result = _gnutls_asn2err (result);
      goto cleanup;
    }

  result = 0;

cleanup:
  asn1_delete_structure (&c2);
  if (val.data != data->data)
    _gnutls_free_datum (&val);
  return result;
}

void
_asnstr_append_name (char *name, size_t name_size, const char *part1,
                     const char *part2)
{
  if (part1[0] != 0)
    {
      _gnutls_str_cpy (name, name_size, part1);
      _gnutls_str_cat (name, name_size, part2);
    }
  else
    _gnutls_str_cpy (name, name_size, part2 + 1 /* remove initial dot */ );
}



/* Encodes and copies the private key parameters into a
 * subjectPublicKeyInfo structure.
 *
 */
int
_gnutls_x509_encode_and_copy_PKI_params (ASN1_TYPE dst,
                                         const char *dst_name,
                                         gnutls_pk_algorithm_t
                                         pk_algorithm, gnutls_pk_params_st * params)
{
  const char *pk;
  gnutls_datum_t der = { NULL, 0 };
  int result;
  char name[128];

  pk = _gnutls_x509_pk_to_oid (pk_algorithm);
  if (pk == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_UNKNOWN_PK_ALGORITHM;
    }

  /* write the OID
   */
  _asnstr_append_name (name, sizeof (name), dst_name, ".algorithm.algorithm");

  result = asn1_write_value (dst, name, pk, 1);
  if (result != ASN1_SUCCESS)
    {
      gnutls_assert ();
      return _gnutls_asn2err (result);
    }

  result = _gnutls_x509_write_pubkey_params (pk_algorithm, params, &der);
  if (result < 0)
    {
      gnutls_assert ();
      return result;
    }

  _asnstr_append_name (name, sizeof (name), dst_name,
                       ".algorithm.parameters");

  result = asn1_write_value (dst, name, der.data, der.size);

  _gnutls_free_datum (&der);

  if (result != ASN1_SUCCESS)
    {
      gnutls_assert ();
      return _gnutls_asn2err (result);
    }

  result = _gnutls_x509_write_pubkey (pk_algorithm, params, &der);
  if (result < 0)
    {
      gnutls_assert ();
      return result;
    }

  /* Write the DER parameters. (in bits)
   */
  _asnstr_append_name (name, sizeof (name), dst_name,
                       ".subjectPublicKey");
  result = asn1_write_value (dst, name, der.data, der.size * 8);
  _gnutls_free_datum (&der);

  if (result != ASN1_SUCCESS)
    {
      gnutls_assert ();
      return _gnutls_asn2err (result);
    }

  return 0;
}

/* Encodes and public key parameters into a
 * subjectPublicKeyInfo structure and stores it in der.
 */
int
_gnutls_x509_encode_PKI_params (gnutls_datum_t *der,
                                gnutls_pk_algorithm_t
                                pk_algorithm, gnutls_pk_params_st * params)
{
  int ret;
  ASN1_TYPE tmp;

  ret = asn1_create_element (_gnutls_get_pkix (),
                                "PKIX1.Certificate", &tmp);
  if (ret != ASN1_SUCCESS)
    {
      gnutls_assert ();
      return _gnutls_asn2err (ret);
    }
    
  ret = _gnutls_x509_encode_and_copy_PKI_params (tmp,
                                         "tbsCertificate.subjectPublicKeyInfo",
                                         pk_algorithm, params);
  if (ret != ASN1_SUCCESS)
    {
      gnutls_assert ();
      ret = _gnutls_asn2err (ret);
      goto cleanup;
    }

  ret = _gnutls_x509_der_encode(tmp, "tbsCertificate.subjectPublicKeyInfo", der, 0);

cleanup:
  asn1_delete_structure (&tmp);

  return ret;
}

/* Reads and returns the PK algorithm of the given certificate-like
 * ASN.1 structure. src_name should be something like "tbsCertificate.subjectPublicKeyInfo".
 */
int
_gnutls_x509_get_pk_algorithm (ASN1_TYPE src, const char *src_name,
                               unsigned int *bits)
{
  int result;
  int algo;
  char oid[64];
  int len;
  gnutls_pk_params_st params;
  char name[128];

  gnutls_pk_params_init(&params);

  _asnstr_append_name (name, sizeof (name), src_name, ".algorithm.algorithm");
  len = sizeof (oid);
  result = asn1_read_value (src, name, oid, &len);

  if (result != ASN1_SUCCESS)
    {
      gnutls_assert ();
      return _gnutls_asn2err (result);
    }

  algo = _gnutls_x509_oid2pk_algorithm (oid);
  if (algo == GNUTLS_PK_UNKNOWN)
    {
      _gnutls_debug_log
        ("%s: unknown public key algorithm: %s\n", __func__, oid);
    }

  if (bits == NULL)
    {
      return algo;
    }

  /* Now read the parameters' bits 
   */
  result = _gnutls_get_asn_mpis(src, src_name, &params);
  if (result < 0)
    return gnutls_assert_val(result);

  bits[0] = pubkey_to_bits(algo, &params);

  gnutls_pk_params_release(&params);
  return algo;
}

/* Reads the DER signed data from the certificate and allocates space and
 * returns them into signed_data.
 */
int
_gnutls_x509_get_signed_data (ASN1_TYPE src, const char *src_name,
                              gnutls_datum_t * signed_data)
{
  gnutls_datum_t der;
  int start, end, result;

  result = _gnutls_x509_der_encode (src, "", &der, 0);
  if (result < 0)
    {
      gnutls_assert ();
      return result;
    }

  /* Get the signed data
   */
  result = asn1_der_decoding_startEnd (src, der.data, der.size,
                                       src_name, &start, &end);
  if (result != ASN1_SUCCESS)
    {
      result = _gnutls_asn2err (result);
      gnutls_assert ();
      goto cleanup;
    }

  result = _gnutls_set_datum (signed_data, &der.data[start], end - start + 1);

  if (result < 0)
    {
      gnutls_assert ();
      goto cleanup;
    }

  result = 0;

cleanup:
  _gnutls_free_datum (&der);

  return result;
}

/*-
 * gnutls_x509_get_signature_algorithm:
 * @src: should contain an ASN1_TYPE structure
 * @src_name: the description of the signature field
 *
 * This function will return a value of the #gnutls_sign_algorithm_t
 * enumeration that is the signature algorithm that has been used to
 * sign this certificate.
 *
 * Returns: a #gnutls_sign_algorithm_t value, or a negative error code on
 *   error.
 -*/
int
_gnutls_x509_get_signature_algorithm (ASN1_TYPE src, const char *src_name)
{
  int result;
  gnutls_datum_t sa;

  /* Read the signature algorithm. Note that parameters are not
   * read. They will be read from the issuer's certificate if needed.
   */
  result =
    _gnutls_x509_read_value (src, src_name, &sa, 0);

  if (result < 0)
    {
      gnutls_assert ();
      return result;
    }

  result = _gnutls_x509_oid2sign_algorithm ( (char*)sa.data);

  _gnutls_free_datum (&sa);

  return result;
}


/* Reads the DER signature from the certificate and allocates space and
 * returns them into signed_data.
 */
int
_gnutls_x509_get_signature (ASN1_TYPE src, const char *src_name,
                            gnutls_datum_t * signature)
{
  int result, len;
  unsigned int bits;

  signature->data = NULL;
  signature->size = 0;

  /* Read the signature 
   */
  len = 0;
  result = asn1_read_value (src, src_name, NULL, &len);

  if (result != ASN1_MEM_ERROR)
    {
      result = _gnutls_asn2err (result);
      gnutls_assert ();
      goto cleanup;
    }

  bits = len;
  if (bits % 8 != 0)
    {
      gnutls_assert ();
      result = GNUTLS_E_CERTIFICATE_ERROR;
      goto cleanup;
    }

  len = bits / 8;

  signature->data = gnutls_malloc (len);
  if (signature->data == NULL)
    {
      gnutls_assert ();
      result = GNUTLS_E_MEMORY_ERROR;
      return result;
    }

  /* read the bit string of the signature
   */
  bits = len;
  result = asn1_read_value (src, src_name, signature->data, (int*)&bits);

  if (result != ASN1_SUCCESS)
    {
      result = _gnutls_asn2err (result);
      gnutls_assert ();
      goto cleanup;
    }

  signature->size = len;

  return 0;

cleanup:
  return result;
}
